package com.lin.cloud.disk.shiro.filter;

import cn.hutool.http.Method;
import com.alibaba.fastjson.JSON;
import com.lin.cloud.disk.common.constant.HttpConstant;
import com.lin.cloud.disk.common.constant.RedisConstant;
import com.lin.cloud.disk.common.exception.BusinessException;
import com.lin.cloud.disk.common.utils.Result;
import com.lin.cloud.disk.common.utils.SpringContextUtil;
import com.lin.cloud.disk.shiro.pojo.JwtToken;
import com.lin.cloud.disk.shiro.utils.JwtUtil;
import com.lin.cloud.disk.common.enums.EResponseCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @program: blog
 * @description: 自定义一个Filter，用来拦截所有的请求判断是否携带Token
 * isAccessAllowed()判断是否携带了有效的JwtToken
 * onAccessDenied()是没有携带JwtToken的时候进行账号密码登录，登录成功允许访问，登录失败拒绝访问
 * @author: Lin
 * @create: 2020-04-03 12:28
 **/
@Slf4j
@Component
public class JwtFilter extends AccessControlFilter {

    private StringRedisTemplate stringRedisTemplate;

    private static List<String> whiteList;

    static {
        whiteList = new ArrayList<>();
        whiteList.add("/api/login");
        whiteList.add("/doc.html");
        whiteList.add("/api/file/uploadImg");
        whiteList.add("/api/company/availableCompany");
        whiteList.add("/api/register");
        whiteList.add("/api/alipay/notify");
    }


    /**
     * 返回true，shiro就直接允许访问url
     * 返回false，shiro才会根据onAccessDenied的方法的返回值决定是否允许访问url
     *
     * @param servletRequest
     * @param servletResponse
     * @param mappedValue
     * @return
     * @throws Exception
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception {
        // 如果请求头中不包含Authorization 则放行 否则拦截
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String requestURI = request.getRequestURI();
        boolean isDirectRelease = false;
        if (Method.OPTIONS.name().equals(request.getMethod())){
            // 如果是预请求 则放行
            isDirectRelease = true;
        }else {
            for (int i = 0; i < whiteList.size(); i++) {
                if (requestURI.startsWith(whiteList.get(i))) {
                    isDirectRelease = true;
                    break;
                }
            }
        }
        return isDirectRelease;

    }


    /**
     * 返回结果为true表明登录通过
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String accessToken = request.getHeader(HttpConstant.REQUEST_HEAD_TOKEN);
        if (null == accessToken) {
            log.error("accessToken不存在");
            throw new BusinessException("accessToken不存在");
        }

        /*
         * 下面就是固定写法
         * */
        try {
            // 获取用户名
            Long userId = JwtUtil.getUserId(accessToken);
            if (stringRedisTemplate == null) {
                stringRedisTemplate = (StringRedisTemplate) SpringContextUtil.getBean(StringRedisTemplate.class);
            }
            // 检查redis是否还存在缓存
            Boolean hasKey = stringRedisTemplate.hasKey(RedisConstant.JWT_ACCESS_TOKEN + userId);
            if (!hasKey) {
                throw new Exception("token过期");
            }


            // 委托 realm 进行登录认证
            //所以这个地方最终还是调用JwtRealm进行的认证
            JwtToken jwtToken = new JwtToken(accessToken);
            getSubject(servletRequest, servletResponse).login(jwtToken);
            //也就是subject.login(token)

            // 刷新jwtToken
            stringRedisTemplate.expire(RedisConstant.JWT_ACCESS_TOKEN + userId, 30, TimeUnit.DAYS);


        } catch (Exception e) {
            // 用下面的方法向客户端返回错误信息
            onLoginFail(servletResponse);
            return false;
        }

        return true;
        //执行方法中没有抛出异常就表示登录成功
    }


    /**
     * 登录失败时默认返回 401 状态码
     *
     * @param response
     * @throws IOException
     */
    private void onLoginFail(ServletResponse response) throws IOException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        httpResponse.setCharacterEncoding("UTF-8");
        httpResponse.setContentType("application/json; charset=utf-8");
        String errorResult = JSON.toJSONString(Result.fail(EResponseCode.LOGON_FAIL));
        Writer out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream()));
        out.write(errorResult);
        out.flush();
        out.close();
    }


}